#ifndef _FILECOMPRESS_H__
#define _FILECOMPRESS_H__


#include <fstream>
#include <string>
#include <assert.h>
#include "HuffmanTree.h"

typedef long long LongType;

struct ConfigInfo//用于解压缩时重建Huffman树，避免多余压缩_code
{
	char _c;//保存字符
	LongType _count;//保存字符出现的次数
};
struct CharInfo
{
	char _c;//保存字符
	LongType _count;//保存字符出现的次数
	string _code;//保存字符转换成的编码，为了保存方便先按照字符串保存
	CharInfo operator + (const CharInfo& info)
	{
		CharInfo parent;
		parent._count = _count + info._count;
		return parent;
	}
	bool operator > (const CharInfo& right)const //const对象要调用const重载的函数
	{	
		return _count > right._count;
	}
	bool operator != (const CharInfo& info)
	{
		return _count != info._count;
	}
};
class FileCompress
{
	typedef HuffmanTreeNode<CharInfo> Node;
public:
	FileCompress()//初始化哈希表
	{
		size_t i = 0;
		for (i = 0; i < 256;++i)
		{
			_hashtable[i]._c = i;
			_hashtable[i]._count = 0;
		}
	}
	void GenerateHuffmanCode(Node* root)//生成Huffman节点
	{
		if (root == NULL)
			return;
		if (root->_left == NULL && root->_right == NULL)
		{
			_hashtable[(unsigned char)root->_value._c]._code = root->_value._code;
			return;
		}
		if (root->_left != NULL)
		{
			root->_left->_value._code = root->_value._code + '0';
			GenerateHuffmanCode(root->_left);
		}
		if (root->_right != NULL)
		{
			root->_right->_value._code = root->_value._code + '1';
			GenerateHuffmanCode(root->_right);
		}

	}

	void GetCharCount(ifstream &ifs, const char*file)
	{
		char c;
		while (ifs.get(c))//从硬盘文件中读取到内存中
		{
			++_hashtable[(unsigned char)c]._count;//直接定址法哈希统计次数，效率高
			//因为哈希表范围是0~255，而字符是-127~128，
			//所以使用直接定址法确定字符在哈希表的位置时，需要强转为（unsigned char）
		}
	}
	void Compress(const char* file)//压缩文件
	{
		ifstream ifs(file,ios::in|ios::binary);//以2进制输入方式打开源文件，文件数据输入到内存中
		//1:统计字符出现的次数
		GetCharCount(ifs, file);
		//2:构建Huffman树
		CharInfo invalid;//筛选出没有插入的字符
		invalid._count = 0;
		HuffmanTree<CharInfo> tree(_hashtable, 256,invalid);
		//3:生成huffman编码
		GenerateHuffmanCode(tree.GetRoot());
		//4:压缩文件

		string compressfile = file;
		compressfile += ".huffman";
		ofstream ofs(compressfile.c_str(),ios::out|ios::binary);//打开文件c.str？,以输出方式打开文件，将内存内容写到文件中
		//4.1:写入字符次数，方便解压缩重建Huffman树
		size_t i = 0;
		for (i = 0; i < 256; ++i)
		{
			if (_hashtable[i]._count > 0)
			{
				ConfigInfo info;
				info._c = _hashtable[i]._c;
				info._count = _hashtable[i]._count;
				ofs.write((const char*)&_hashtable[i], sizeof(ConfigInfo));//将字符和出现的次数都能压缩在文件中
			}
		}
		ConfigInfo end;
		end._count = 0;//区分字符次数和编码的界线
		ofs.write((const char*)&end, sizeof(ConfigInfo));

		ifs.clear();
		ifs.seekg(0);//设置为从头开始读文件
		char c;
		char value = 0;
		int pos = 0;//记录编码位置，每8个bit位存储
		while (ifs.get(c))//将文件从硬盘读到内存中
		{
			string& code = _hashtable[(unsigned char)c]._code;//取一个字符的编码，一位一位取，取八位并且保存到value中（即一字节），存储在文件中
			for (i = 0; i < code.size(); ++i)
			{
				if (code[i] == '0')
				{
					value &= (~(1 << pos));
					++pos;
				}
				else if (code[i] == '1')
				{
					value |= (1 << pos);
					++pos;
				}
				else
					assert(false);//表示出错

				if (pos == 8)//满8位写入压缩文件中去
				{
					ofs.put(value);
					pos = 0;
					value = 0;
				}
			}

		}
		if (pos < 8)
		{
			ofs.put(value);
		}
	}


	//5:解压缩文件
	void UnCompress(const char* file)
	{
		ifstream ifs(file, ios::in | ios::binary); //以2进制输入方式打开源文件，（避免读入到特殊字符，而停止，导致解压缩不完全）
		string uncompress = file;
		int pos = uncompress.rfind(".");
		assert(pos != string::npos);
		uncompress.erase(pos);
		uncompress += ".uncompressfile";
		ofstream ofs(uncompress.c_str(), ios::out | ios::binary);//c_str()函数是返回c形式的字符串，

		//1.重建Huffman树
		ConfigInfo info;
		while (1)//获取字符出现的次数
		{
			ifs.read((char*)&info, sizeof(ConfigInfo));//以分界点为标准，在分界线以前取字符出现的次数
			if (info._count > 0)
			{
				_hashtable[(unsigned char)info._c]._count = info._count;
			}
			else
			{
				break;//到达分界线，跳出
			}
		}
		CharInfo invalid;
		invalid._count = 0;
		HuffmanTree<CharInfo> tree(_hashtable, 256, invalid);
		//2.解压缩文件，将二进制字符转化为对应字符
		Node* root = tree.GetRoot();//得到根节点
		LongType CharCount = root->_value._count;//字符的总数
		char c;
		Node* cur = root;
		int i = 0;
		while (ifs.get(c))
		{
			for (i = 0; i < 8; ++i)
			{
				if ((unsigned char)c & (1 << i))//表示该位为1，向左走，按照编码途径走
				{
					cur = cur->_right;
				}
				else
				{
					cur = cur->_left;
				}
				if (cur->_left == NULL && cur->_right == NULL)
				{
					ofs.put(cur->_value._c);
					cur = root;
					if (--CharCount == 0)//字符次数减为0时，说明解压缩完毕，跳出循环
					{
						break;
					}
				}
			}
		}


	}

private:
	CharInfo _hashtable[256];//结构体数组，数组的每一个元素保存三个信息
};

void TestCompress()
{
	FileCompress fc;
	fc.Compress("Input.txt");
}
void TestUnCompress()
{
	FileCompress fc;
	fc.UnCompress("Input.txt.huffman");
}
#endif//_FILECOMPRESS_HH__